/**
 * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright ownership. Apereo
 * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the License at the
 * following location:
 *
 * <p>http://www.apache.org/licenses/LICENSE-2.0
 *
 * <p>Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apereo.portal.utils.web;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.portlet.PortletRequest;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.portlet.context.PortletRequestAttributes;
import org.springframework.web.util.WebUtils;

/** */
public final class PortalWebUtils {
    private PortalWebUtils() {}

    /** Key for the mutex request session attribute */
    public static final String REQUEST_MUTEX_ATTRIBUTE = PortalWebUtils.class.getName() + ".MUTEX";

    /**
     * Return the best available mutex for the given request attributes: that is, an object to
     * synchronize on for the given request attributes.
     *
     * <p>Returns the request attributes mutex attribute if available; usually, this means that the
     * RequestAttributeMutexListener needs to be defined in <code>web.xml</code>. Falls back to the
     * ServletRequest itself if no mutex attribute found.
     *
     * <p>The request attributes mutex is guaranteed to be the same object during the entire
     * lifetime of the request, available under the key defined by the <code>REQUEST_MUTEX_ATTRIBUTE
     * </code> constant. It serves as a safe reference to synchronize on for locking on the current
     * request attributes.
     *
     * @param servletRequest the ServletRequest to find a mutex for
     * @return the mutex object (never <code>null</code>)
     * @see #REQUEST_MUTEX_ATTRIBUTE
     */
    public static Object getRequestAttributeMutex(ServletRequest servletRequest) {
        Assert.notNull(servletRequest, "ServletRequest must not be null");
        Object mutex = servletRequest.getAttribute(REQUEST_MUTEX_ATTRIBUTE);
        if (mutex == null) {
            mutex = servletRequest;
        }
        return mutex;
    }

    /**
     * Get a {@link ConcurrentMap} for the specified name from the {@link ServletRequest}
     * attributes. If it doesn't exist create it and store it in the attributes. This is done in a
     * thread-safe matter that ensures only one Map per name & request will be created @See {@link
     * #getRequestAttributeMutex(ServletRequest)}
     */
    public static <K, V> ConcurrentMap<K, V> getMapRequestAttribute(
            ServletRequest servletRequest, String name) {
        return getMapRequestAttribute(servletRequest, name, true);
    }

    @SuppressWarnings("unchecked")
    public static <K, V> ConcurrentMap<K, V> getMapRequestAttribute(
            ServletRequest servletRequest, String name, boolean create) {
        final Object mutex = getRequestAttributeMutex(servletRequest);
        synchronized (mutex) {
            ConcurrentMap<K, V> map = (ConcurrentMap<K, V>) servletRequest.getAttribute(name);
            if (map == null) {
                if (!create) {
                    return null;
                }

                map = new ConcurrentHashMap<K, V>();
                servletRequest.setAttribute(name, map);
            }
            return map;
        }
    }

    /**
     * Get a {@link ConcurrentMap} for the specified name from the {@link HttpSession} attributes.
     * If it doesn't exist create it and store it in the attributes. This is done in a thread-safe
     * matter that ensures only one Map per name & session will be created @See {@link
     * WebUtils#getSessionMutex(HttpSession)}
     */
    public static <K, V> ConcurrentMap<K, V> getMapSessionAttribute(
            HttpSession session, String name) {
        return getMapSessionAttribute(session, name, true);
    }

    @SuppressWarnings("unchecked")
    public static <K, V> ConcurrentMap<K, V> getMapSessionAttribute(
            HttpSession session, String name, boolean create) {
        final Object mutex = WebUtils.getSessionMutex(session);
        synchronized (mutex) {
            ConcurrentMap<K, V> map = (ConcurrentMap<K, V>) session.getAttribute(name);
            if (map == null) {
                if (!create) {
                    return null;
                }

                map = new ConcurrentHashMap<K, V>();
                session.setAttribute(name, map);
            }
            return map;
        }
    }

    /**
     * Get the request context path from the current request. Copes with both HttpServletRequest and
     * PortletRequest and so usable when handling Spring-processed Servlet or Portlet requests.
     * Requires that Spring have bound the request, as in the case of dispatcher servlet or portlet
     * or when the binding filter or listener is active. This should be the case for all requests in
     * the uPortal framework and framework portlets.
     *
     * @return request.getContextPath() for the relevant servlet or portlet request
     * @throws IllegalStateException if the request is not Spring-bound or is neither Servlet nor
     *     Portlet flavored
     */
    public static String currentRequestContextPath() {

        final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        if (null == requestAttributes) {
            throw new IllegalStateException(
                    "Request attributes are not bound.  "
                            + "Not operating in context of a Spring-processed Request?");
        }

        if (requestAttributes instanceof ServletRequestAttributes) {

            final ServletRequestAttributes servletRequestAttributes =
                    (ServletRequestAttributes) requestAttributes;
            final HttpServletRequest request = servletRequestAttributes.getRequest();
            return request.getContextPath();

        } else if (requestAttributes instanceof PortletRequestAttributes) {

            final PortletRequestAttributes portletRequestAttributes =
                    (PortletRequestAttributes) requestAttributes;
            final PortletRequest request = portletRequestAttributes.getRequest();
            return request.getContextPath();

        } else {
            throw new IllegalStateException(
                    "Request attributes are an unrecognized implementation.");
        }
    }
}
